window.ctMaxUploadFiles = 1; var _ctCrop = (function() { var state = { fileName: '', blob: null, image: null, imageUrl: null, outputMime: 'image/png', outputExt: '.png', baseName: 'image', selection: null, scale: 1, canvasW: 0, canvasH: 0 }; var drag = { mode: null, handle: null, startCanvasX: 0, startCanvasY: 0, startImageX: 0, startImageY: 0, anchorX: 0, anchorY: 0, startSel: null }; var stage = null; var stageCtx = null; var eventsBound = false; var isUpdatingFields = false; var minSize = 12; function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } function initElements() { if (!stage) { stage = document.getElementById('crop-stage'); if (stage) stageCtx = stage.getContext('2d'); } bindEvents(); } function bindEvents() { if (eventsBound || !stage) return; stage.addEventListener('pointerdown', onPointerDown); stage.addEventListener('pointermove', onPointerMove); stage.addEventListener('pointerup', onPointerUp); stage.addEventListener('pointercancel', onPointerUp); stage.addEventListener('dblclick', function() { if (!state.image) return; setDefaultSelection(); draw(); }); $(document).on('click', '#crop-reset', function() { if (!state.image) return; setDefaultSelection(); draw(); }); $(document).on('click', '#crop-apply', function() { applyCrop(); }); $(document).on('input change', '#crop-x, #crop-y, #crop-width, #crop-height', function() { if (isUpdatingFields || !state.image) return; applyFieldSelection(); }); $(window).on('resize', function() { if (!state.image) return; fitCanvasToContainer(); draw(); }); eventsBound = true; } function detectOutput(blob) { var mime = (blob && blob.type ? blob.type.toLowerCase() : ''); if (mime !== 'image/jpeg' && mime !== 'image/png' && mime !== 'image/webp') { mime = 'image/png'; } state.outputMime = mime || 'image/png'; state.outputExt = state.outputMime === 'image/jpeg' ? '.jpg' : (state.outputMime === 'image/webp' ? '.webp' : '.png'); } function baseName(fileName) { var name = fileName || 'image'; var b = name.replace(/\.[^.]+$/, ''); if (!b || b === name) { b = name + '-cropped'; } return b; } function roundedSelection() { if (!state.selection || !state.image) return null; var x = Math.max(0, Math.round(state.selection.x)); var y = Math.max(0, Math.round(state.selection.y)); var w = Math.max(1, Math.round(state.selection.w)); var h = Math.max(1, Math.round(state.selection.h)); if (x >= state.image.width) x = state.image.width - 1; if (y >= state.image.height) y = state.image.height - 1; if (x + w > state.image.width) w = state.image.width - x; if (y + h > state.image.height) h = state.image.height - y; return { x: x, y: y, w: Math.max(1, w), h: Math.max(1, h) }; } function normalizeSelection(sel) { if (!state.image) return sel; var maxW = Math.max(1, state.image.width); var maxH = Math.max(1, state.image.height); var w = Math.max(1, sel.w); var h = Math.max(1, sel.h); w = Math.min(w, maxW); h = Math.min(h, maxH); var x = clamp(sel.x, 0, maxW - w); var y = clamp(sel.y, 0, maxH - h); return { x: x, y: y, w: w, h: h }; } function setDefaultSelection() { if (!state.image) return; var padX = Math.round(state.image.width * 0.08); var padY = Math.round(state.image.height * 0.08); var w = state.image.width - (padX * 2); var h = state.image.height - (padY * 2); if (w < minSize || h < minSize) { padX = 0; padY = 0; w = state.image.width; h = state.image.height; } state.selection = normalizeSelection({ x: padX, y: padY, w: Math.max(minSize, w), h: Math.max(minSize, h) }); syncFieldsAndMeta(); } function fitCanvasToContainer() { if (!state.image || !stage) return; var wrap = stage.parentElement; var maxWidth = wrap ? wrap.clientWidth - 16 : 720; if (!isFinite(maxWidth) || maxWidth < 200) maxWidth = 720; var maxHeight = Math.min(680, Math.max(280, Math.floor(window.innerHeight * 0.65))); var scale = Math.min(maxWidth / state.image.width, maxHeight / state.image.height, 1); if (!isFinite(scale) || scale <= 0) scale = 1; state.scale = scale; state.canvasW = Math.max(1, Math.round(state.image.width * scale)); state.canvasH = Math.max(1, Math.round(state.image.height * scale)); stage.width = state.canvasW; stage.height = state.canvasH; stage.style.width = state.canvasW + 'px'; stage.style.height = state.canvasH + 'px'; } function getCanvasPoint(e) { var rect = stage.getBoundingClientRect(); return { x: clamp(e.clientX - rect.left, 0, state.canvasW), y: clamp(e.clientY - rect.top, 0, state.canvasH) }; } function canvasToImagePoint(x, y) { return { x: x / state.scale, y: y / state.scale }; } function selectionCanvasRect() { var s = state.selection || { x: 0, y: 0, w: 0, h: 0 }; return { x: s.x * state.scale, y: s.y * state.scale, w: s.w * state.scale, h: s.h * state.scale }; } function handlePoints() { var r = selectionCanvasRect(); var cx = r.x + (r.w / 2); var cy = r.y + (r.h / 2); return { nw: { x: r.x, y: r.y }, n: { x: cx, y: r.y }, ne: { x: r.x + r.w, y: r.y }, e: { x: r.x + r.w, y: cy }, se: { x: r.x + r.w, y: r.y + r.h }, s: { x: cx, y: r.y + r.h }, sw: { x: r.x, y: r.y + r.h }, w: { x: r.x, y: cy } }; } function getHandleAt(x, y) { var handles = handlePoints(); var radius = 10; var keys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; for (var i = 0; i < keys.length; i++) { var k = keys[i]; var p = handles[k]; if (!p) continue; if (Math.abs(x - p.x) <= radius && Math.abs(y - p.y) <= radius) { return k; } } return null; } function pointInSelection(x, y) { var r = selectionCanvasRect(); return x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h; } function cursorFor(handle, x, y) { if (handle === 'nw' || handle === 'se') return 'nwse-resize'; if (handle === 'ne' || handle === 'sw') return 'nesw-resize'; if (handle === 'n' || handle === 's') return 'ns-resize'; if (handle === 'e' || handle === 'w') return 'ew-resize'; if (pointInSelection(x, y)) return 'move'; return 'crosshair'; } function onPointerDown(e) { if (!state.image || !stage) return; var p = getCanvasPoint(e); var imgPoint = canvasToImagePoint(p.x, p.y); var handle = getHandleAt(p.x, p.y); drag.startCanvasX = p.x; drag.startCanvasY = p.y; drag.startImageX = imgPoint.x; drag.startImageY = imgPoint.y; drag.startSel = { x: state.selection.x, y: state.selection.y, w: state.selection.w, h: state.selection.h }; if (handle) { drag.mode = 'resize'; drag.handle = handle; } else if (pointInSelection(p.x, p.y)) { drag.mode = 'move'; drag.handle = null; } else { drag.mode = 'new'; drag.handle = null; drag.anchorX = clamp(imgPoint.x, 0, state.image.width); drag.anchorY = clamp(imgPoint.y, 0, state.image.height); state.selection = normalizeSelection({ x: drag.anchorX, y: drag.anchorY, w: 1, h: 1 }); syncFieldsAndMeta(); draw(); } if (stage.setPointerCapture && typeof e.pointerId !== 'undefined') { try { stage.setPointerCapture(e.pointerId); } catch (err) { } } e.preventDefault(); } function onPointerMove(e) { if (!state.image || !stage) return; var p = getCanvasPoint(e); if (!drag.mode) { var hoveredHandle = getHandleAt(p.x, p.y); stage.style.cursor = cursorFor(hoveredHandle, p.x, p.y); return; } if (drag.mode === 'move') { var dx = (p.x - drag.startCanvasX) / state.scale; var dy = (p.y - drag.startCanvasY) / state.scale; state.selection = normalizeSelection({ x: drag.startSel.x + dx, y: drag.startSel.y + dy, w: drag.startSel.w, h: drag.startSel.h }); } else if (drag.mode === 'resize') { var current = canvasToImagePoint(p.x, p.y); var l = drag.startSel.x; var t = drag.startSel.y; var r = drag.startSel.x + drag.startSel.w; var b = drag.startSel.y + drag.startSel.h; if (drag.handle.indexOf('w') >= 0) l = clamp(current.x, 0, r - minSize); if (drag.handle.indexOf('e') >= 0) r = clamp(current.x, l + minSize, state.image.width); if (drag.handle.indexOf('n') >= 0) t = clamp(current.y, 0, b - minSize); if (drag.handle.indexOf('s') >= 0) b = clamp(current.y, t + minSize, state.image.height); state.selection = normalizeSelection({ x: l, y: t, w: r - l, h: b - t }); } else if (drag.mode === 'new') { var currentPoint = canvasToImagePoint(p.x, p.y); var nx = clamp(currentPoint.x, 0, state.image.width); var ny = clamp(currentPoint.y, 0, state.image.height); var left = Math.min(drag.anchorX, nx); var top = Math.min(drag.anchorY, ny); var right = Math.max(drag.anchorX, nx); var bottom = Math.max(drag.anchorY, ny); state.selection = normalizeSelection({ x: left, y: top, w: Math.max(1, right - left), h: Math.max(1, bottom - top) }); } syncFieldsAndMeta(); draw(); e.preventDefault(); } function onPointerUp(e) { if (stage && stage.releasePointerCapture && typeof e.pointerId !== 'undefined') { try { stage.releasePointerCapture(e.pointerId); } catch (err) { } } drag.mode = null; drag.handle = null; if (stage) stage.style.cursor = 'crosshair'; } function draw() { if (!state.image || !stageCtx || !stage) return; stageCtx.clearRect(0, 0, state.canvasW, state.canvasH); stageCtx.drawImage(state.image, 0, 0, state.canvasW, state.canvasH); var r = selectionCanvasRect(); var right = r.x + r.w; var bottom = r.y + r.h; stageCtx.fillStyle = 'rgba(18,24,20,0.45)'; stageCtx.fillRect(0, 0, state.canvasW, r.y); stageCtx.fillRect(0, r.y, r.x, r.h); stageCtx.fillRect(right, r.y, state.canvasW - right, r.h); stageCtx.fillRect(0, bottom, state.canvasW, state.canvasH - bottom); stageCtx.strokeStyle = '#ffffff'; stageCtx.lineWidth = 2; stageCtx.strokeRect(r.x, r.y, r.w, r.h); stageCtx.strokeStyle = '#2d7a2d'; stageCtx.lineWidth = 1; stageCtx.strokeRect(r.x + 0.5, r.y + 0.5, Math.max(0, r.w - 1), Math.max(0, r.h - 1)); stageCtx.strokeStyle = 'rgba(255,255,255,0.85)'; stageCtx.lineWidth = 1; stageCtx.beginPath(); stageCtx.moveTo(r.x + (r.w / 3), r.y); stageCtx.lineTo(r.x + (r.w / 3), bottom); stageCtx.moveTo(r.x + (r.w * 2 / 3), r.y); stageCtx.lineTo(r.x + (r.w * 2 / 3), bottom); stageCtx.moveTo(r.x, r.y + (r.h / 3)); stageCtx.lineTo(right, r.y + (r.h / 3)); stageCtx.moveTo(r.x, r.y + (r.h * 2 / 3)); stageCtx.lineTo(right, r.y + (r.h * 2 / 3)); stageCtx.stroke(); var handles = handlePoints(); var keys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; var hr = 4.5; for (var i = 0; i < keys.length; i++) { var p = handles[keys[i]]; stageCtx.beginPath(); stageCtx.arc(p.x, p.y, hr, 0, Math.PI * 2); stageCtx.fillStyle = '#ffffff'; stageCtx.fill(); stageCtx.strokeStyle = '#2d7a2d'; stageCtx.lineWidth = 1.5; stageCtx.stroke(); } } function updateLegacyInputs(sel) { var inputs = $('.extra-input input'); if (!inputs || inputs.length < 4) return; $(inputs[0]).val(sel.x); $(inputs[1]).val(sel.y); $(inputs[2]).val(sel.w); $(inputs[3]).val(sel.h); } function syncFieldsAndMeta() { var sel = roundedSelection(); if (!sel || !state.image) return; isUpdatingFields = true; $('#crop-x').val(sel.x); $('#crop-y').val(sel.y); $('#crop-width').val(sel.w); $('#crop-height').val(sel.h); isUpdatingFields = false; updateLegacyInputs(sel); $('#crop-file-label').text(state.fileName || 'image'); $('#crop-original-size').text(state.image.width + ' x ' + state.image.height + ' px'); } function applyFieldSelection() { if (!state.image) return; var x = parseInt($('#crop-x').val(), 10); var y = parseInt($('#crop-y').val(), 10); var w = parseInt($('#crop-width').val(), 10); var h = parseInt($('#crop-height').val(), 10); if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h)) return; state.selection = normalizeSelection({ x: x, y: y, w: Math.max(1, w), h: Math.max(1, h) }); syncFieldsAndMeta(); draw(); } function applyCrop() { if (!state.image || !state.selection) return; var sel = roundedSelection(); if (!sel || sel.w <= 0 || sel.h <= 0) { alert('Please choose a valid crop area.'); return; } var canvas = document.createElement('canvas'); canvas.width = sel.w; canvas.height = sel.h; var ctx = canvas.getContext('2d'); ctx.drawImage(state.image, sel.x, sel.y, sel.w, sel.h, 0, 0, sel.w, sel.h); canvas.toBlob(function(outBlob) { if (!outBlob) { alert('Could not crop this image file.'); return; } add_file_output( URL.createObjectURL(outBlob), state.baseName + '-cropped' + state.outputExt ); }, state.outputMime, 0.92); } function loadFile(blob, fileName) { initElements(); if (!stage || !stageCtx) { alert('Crop editor could not be initialized in this browser.'); return; } var url = URL.createObjectURL(blob); var img = new Image(); img.onload = function() { if (state.imageUrl) URL.revokeObjectURL(state.imageUrl); state.imageUrl = url; state.image = img; state.blob = blob; state.fileName = fileName || 'image'; state.baseName = baseName(state.fileName); detectOutput(blob); $('#file-drop-zone').addClass('collapsedDropZone'); (function(){ var $b=$('#file-drop-zone .upload-file-button'); var l=$b.data('change-label'); if(l) $b.text(l); })(); $('#crop-workspace').show(); fitCanvasToContainer(); setDefaultSelection(); draw(); }; img.onerror = function() { URL.revokeObjectURL(url); alert('Could not open this image file.'); }; img.src = url; } return { loadFile: loadFile }; })(); function processFile(blob, fileName) { _ctCrop.loadFile(blob, fileName); } var _loadedScripts = {}; function loadScriptPromise(url) { if (_loadedScripts[url]) return _loadedScripts[url]; _loadedScripts[url] = new Promise(function (resolve, reject) { var s = document.createElement('script'); s.src = url; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); return _loadedScripts[url]; } function replaceAll(find, replace, str) { return str.replace(new RegExp(find, 'g'), replace); } function beautify(str) { var result = ''; var length = str.length; var i = 0; var braceCountLeft = 0; var braceCountRight = 0; var withinQuotes = false; while (i < length) { var c = str[i]; if (c == '"' && (i == 0 || c[i - 1] != '\\')) { // non-escaped quotes withinQuotes = !withinQuotes; } if (!withinQuotes && (c == '}' || c == '{' || c == ',')) { console.log('Start####' + result); // look back and remove carriage returns and whitespace that are already there var resultIndex = result.length - 1; while (resultIndex >= 0 && (result[resultIndex] == ' ' || result[resultIndex] == '\r' || result[resultIndex] == '\n' || result[resultIndex] == '\t')) { resultIndex = resultIndex - 1; result = result.substr(0, resultIndex + 1); console.log('char ' + result[resultIndex] + '-----' + result + 'zzz ' + result.length + ' ' + resultIndex); } if (c == '{') { braceCountLeft++; result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } else if (c == '}') { braceCountRight++; // precede with carriage return result += '\r' + GetTabs(braceCountLeft - braceCountRight) + c; } else if (c == ',') { result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } var nextChar = ''; // advance through whitespace and remove carriage returns that are already there while (i < length && (str[i + 1] == ' ' || str[i + 1] == '\r' || str[i + 1] == '\n' || str[i + 1] == '\t')) { i++; } } else { result += str[i]; } i++; } return result; } function GetTabs(count) { var result = ''; for (var i = 0; i < count; i++) { result += ' '; } return result; }